"""
The MIT License (MIT)
Copyright © 2020 Walkline Wang (https://walkline.wang)
Gitee: https://gitee.com/walkline/ESP32-BLE-Remote_Controller
"""
import ubluetooth as bt
import struct
from ble.profile.manager import ProfileManager
from ble.services import *
from ble.characteristics import *
from ble.descriptors import *
from ble.tools import BLETools
from ble.const import BLEConst


PACK = struct.pack

"""
REPORT_MAP_DATA 描述符了一个用户自定义的控制设备

用途为控制音量加减
"""
REPORT_MAP_DATA = [
	0x05, 0x0C, #	USAGE_PAGE (Consumer Devices)
	0x09, 0x01, #	USAGE (Consumer Control)
	0xA1, 0x01, #	COLLECTION (Application)

	# 定义 Report ID 和逻辑最大最小值
	0x85, 0x01, #		REPORT_ID (1)
	0x15, 0x00, #		LOGICAL_MINIMUM (0)
	0x25, 0x01, #		LOGICAL_MAXIMUM (1)

	# 定义 Report Count 为 1 bit，即只有 1 个数据段
	0x95, 0x01, #		REPORT_COUNT (1)
	# 定义 Report Size 为 1 bit，即每个数据段只能为 0 或 1，因此逻辑值为 0 和 1
	0x75, 0x01, #		REPORT_SIZE (1)
	
	# 定义用途为 音量减，控制位为 0b1
	0x09, 0xEA, #		USAGE (Volume Down)
	0x81, 0x06, #		INPUT (Data,Var,Rel)

	# 定义用途为 音量加，控制位为 0b10
	0x09, 0xE9, #		USAGE (Volume Up)
	0x81, 0x06, #		INPUT (Data,Var,Rel)

	# 补充 7 bit 空位常量凑足 1 byte
	# 8 bit - (Report Count * Report Size)
	0x95, 0x01, #		REPORT_COUNT (1)
	0x75, 0x07, #		REPORT_SIZE (7)
	0x81, 0x03, #		INPUT (Cnst,Var,Rel)

	0xC0		#	END_COLLECTION
]


class BLERemoteController(object):
	def __init__(self, device_name="RemoteCon"):
		self.__ble = bt.BLE()
		self.__device_name = device_name
		self.__conn_handle = None

		self.__write = self.__ble.gatts_write
		self.__read = self.__ble.gatts_read
		self.__notify = self.__ble.gatts_notify

		self.__profile_manager_adv = ProfileManager()
		self.__profile_manager_resp = ProfileManager()

		# 定义 HID 设备必须的 3 个服务
		self.__profile_manager_adv.add_services(
			HumanInterfaceDevice().add_characteristics(
				ReportMap(),
				Report(),
				ProtocolMode(),
				HIDInformation(),
				HIDControlPoint(),
				BootMouseInputReport(),
			),
			DeviceInformation().add_characteristics(
				PNPID(),
			),
			BatteryService().add_characteristics(
				BatteryLevel(),
			),
		)

		# 定义 BLE 设备必须的 2 个服务
		self.__profile_manager_resp.add_services(
			GenericAccess().add_characteristics(
				DeviceName(),
				Appearance(),
				PeripheralPreferredConnectionParameters(),
				CentralAddressResolution(),
			),
			GenericAttribute().add_characteristics(
				ServiceChanged(),
			),
		)

	
		self.__ble.active(False)
		print("activating ble...")
		self.__ble.active(True)
		print("ble activated")

		self.__services = self.__profile_manager_adv.get_services() + self.__profile_manager_resp.get_services()
		self.__ble.irq(self.__irq)
		self.__register_services()

		self.__adv_payload = BLETools.advertising_hid_payload(
			services=self.__profile_manager_adv.get_services_uuid(),
			appearance=BLEConst.Appearance.HUMAN_INTERFACE_DEVICE_HID
		)
		self.__resp_payload = BLETools.advertising_resp_payload(
			services=self.__profile_manager_resp.get_services_uuid(),
			name=self.__device_name
		)

		self.__advertise()

	def __register_services(self):
		(
			(
				self.__handle_report_map,
				self.__handle_report,
				self.__handle_protocol_mode,
				self.__handle_hid_information,
				self.__handle_hid_control_point,
				self.__handle_boot_mouse_input_report,
			),
			(
				self.__handle_pnp_id,
			),
			(
				self.__handle_battery_level,
			),
			(
				self.__handle_device_name,
				self.__handle_appearance,
				self.__handle_ppcp,
				self.__handle_central_address_resolution,
			),
			(
				self.__handle_service_changed,
			),
		) = self.__ble.gatts_register_services(self.__services)

		print("services registed")

		self.__setup_generic_access()
		self.__setup_hid()
		self.__setup_device_info()
		self.update_battery_level()

	def __advertise(self, interval_us=100000):
		self.__ble.gap_advertise(None)
		self.__ble.gap_advertise(interval_us, adv_data=self.__adv_payload, resp_data=self.__resp_payload)
		print("advertising...")

	def __irq(self, event, data):
		if event == BLEConst.IRQ.IRQ_CENTRAL_CONNECT:
			self.__conn_handle, _, addr, = data
			print("[{}] connected, handle: {}".format(BLETools.decode_mac(addr), self.__conn_handle))

			self.__ble.gap_advertise(None)
		elif event == BLEConst.IRQ.IRQ_CENTRAL_DISCONNECT:
			self.__conn_handle, _, addr, = data
			print("[{}] disconnected, handle: {}".format(BLETools.decode_mac(addr), self.__conn_handle))

			self.__conn_handle = None
			self.__advertise()
		else:
			print("event: {}, data: {}".format(event, data))

	def __setup_hid(self):
		self.__write(self.__handle_report_map, bytes(REPORT_MAP_DATA))
		self.__write(self.__handle_protocol_mode, PACK("<B", 1))
		self.__write(self.__handle_hid_information, PACK("<Hbb", 0x0100, 0x00, 0b0011))

	def __setup_device_info(self):
		self.__write(self.__handle_pnp_id, PACK("<BHHH", 1, 0x02E5, 0x01, 0x01)) # 由 Bluetooth SIG 分配的公司代码, 乐鑫信息科技, PID, PVer

	def __setup_generic_access(self):
		self.__write(self.__handle_device_name, PACK("<{}s".format(len(self.__device_name)), self.__device_name.encode()))
		self.__write(self.__handle_appearance, PACK("<h", BLEConst.Appearance.HUMAN_INTERFACE_DEVICE_HID))
		self.__write(self.__handle_ppcp, PACK("<4h", 40, 80, 10, 300))
		self.__write(self.__handle_central_address_resolution, PACK("<b", 1))

	def update_battery_level(self):
		import random

		random.seed(random.randint(-2**16, 2**16))
		self.__write(self.__handle_battery_level, PACK("<B", int(random.randint(1, 100))))

		if self.__conn_handle is not None:
			self.__notify(self.__conn_handle, self.__handle_battery_level)

	def send_volume_key(self):
		if self.__conn_handle is not None:
			self.__write(self.__handle_report, bytes([0x1, 0b1])) # 0x1 为 REPORT_MAP_DATA 中定义的 Report ID
			self.__notify(self.__conn_handle, self.__handle_report)

			# 发送 0b0 释放按键
			self.__write(self.__handle_report, bytes([0x1, 0b0]))
			self.__notify(self.__conn_handle, self.__handle_report)


def main():
	from machine import Pin

	def button_click_cb(p):
		remote_controller.send_volume_key()

	remote_controller = BLERemoteController()

	button = Pin(0, Pin.IN, Pin.PULL_UP)
	button.irq(button_click_cb, Pin.IRQ_RISING)

	print("button initialized")


if __name__ == "__main__":
	main()
